/*

Main.java

*/

package main;
import java.util.ArrayList;
import java.util.Collection;

import model.Resultado;
import util.EstatisticaTeorica;
import controller.Controlador;
import controller.Eventos;

public class Main 
{
	public static void main(String[] args) 
	{		
		rodadaTaxaChegada(0.1f);
		rodadaTaxaChegada(0.2f);
		rodadaTaxaChegada(0.3f);
		rodadaTaxaChegada(0.4f);
		rodadaTaxaChegada(0.45f);
	}

	public static void rodadaTaxaChegada(float taxa_chegada)
	{
		System.out.println("Taxa = " + taxa_chegada + "; Fator: " + Config.fator() +"\n");
		
		Collection <Resultado> resultadosDasRodadas = new ArrayList<Resultado>();

		for(int i = 0 ; i < Config.NUMERO_RODADAS ; i++)
		{
			// Multiplicamos por um numero suficientemente grande
			// pra garantir que a semente não se repita entre
			// diferentes rodadas			
			long semente = (long)(Math.random() * 2000000);

			// Instancio o Controlador com a semente, o lambda passado por parametro
			// e a taxa de servico do servidor, que no caso eh um
			Controlador controlador = new Controlador(semente, taxa_chegada, 1.0f);

			try 
			{
				// Tento executar a rodada, caso contrario, volto
				// e tento novamente
				Resultado resultado = controlador.executarRodada();
				
				// Adiciono o resultado da Rodada em um Array de Resultados
				resultadosDasRodadas.add(resultado);	
			} 
			catch (Exception e) 
			{
				i--;
			}
		
			// Limpa o Singleton que Gerencia os Eventos para
			// rodarmos uma nova rodada limpa
			Eventos.getInstance().removeTodosEventos();
		}

		Collection<Double> 	w1 = new ArrayList<Double>(),
							w2 = new ArrayList<Double>(),
							t1 = new ArrayList<Double>(),
							t2 = new ArrayList<Double>(),
							nq1 = new ArrayList<Double>(),
							nq2 = new ArrayList<Double>(),
							n1 = new ArrayList<Double>(),
							n2 = new ArrayList<Double>(),
							variancias1 = new ArrayList<Double>(),
							variancias2 = new ArrayList<Double>();

		// Percorro toda a lista que contem os resultados
		// e os separo nos tempos de suas respectivas variáveis
		for(Resultado res : resultadosDasRodadas)
		{
			w1.add(res.getTempoFila1());
			w2.add(res.getTempoFila2());
			t1.add(res.getTempoTotalFila1());
			t2.add(res.getTempoTotalFila2());
			nq1.add(res.getTamanhoFila1());
			nq2.add(res.getTamanhoFila2());
			n1.add(res.getTamanhoTotalFila1());
			n2.add(res.getTamanhoTotalFila2());
			variancias1.add(res.getVarianciaTempoFila1());
			variancias2.add(res.getVarianciaTempoFila2());
		}

		EstatisticaTeorica.setTaxaChegada(taxa_chegada);
		
		System.out.println("N1: ");
		System.out.println("Teórico: " + EstatisticaTeorica.n1_t());				
		intervaloDeConfianca(n1,"n1");

		System.out.println("N2: ");
		System.out.println("Teórico: " + EstatisticaTeorica.n2_t());
		intervaloDeConfianca(n2,"n2");

		System.out.println("T1: ");
		System.out.println("Teórico: " + EstatisticaTeorica.t1_t());
		intervaloDeConfianca(t1,"t1");

		System.out.println("T2: ");
		System.out.println("Teórico: " + EstatisticaTeorica.t2_t());
		intervaloDeConfianca(t2,"t1");		

		System.out.println("Nq1: ");
		System.out.println("Teórico: " + EstatisticaTeorica.nq1_t());
		intervaloDeConfianca(nq1,"nq1");

		System.out.println("Nq2: ");
		System.out.println("Teórico: " + EstatisticaTeorica.nq2_t());
		intervaloDeConfianca(nq2,"nq2");

		System.out.println("W1: ");
		System.out.println("Teórico: " + EstatisticaTeorica.w1_t());
		intervaloDeConfianca(w1,"w1");

		System.out.println("W2: ");
		System.out.println("Teórico: " + EstatisticaTeorica.w2_t());
		intervaloDeConfianca(w2,"w2");

		System.out.println("Var(W1): ");
		System.out.println("Teórico: " + EstatisticaTeorica.var_w1_t());
		intervaloDeConfianca(variancias1,"varw1");

		System.out.println("Var(W2): ");
		intervaloDeConfianca(variancias2,"varw2");
	}
	
	// Calcula o intervalo de Confiança das Médias
	private static void intervaloDeConfianca(Collection<Double> medias, String variavel)
	{
		double mediaDasMedias = 0.0d,
				somatorioVariancia = 0.0d,
				desvioPadrao = 0.0d,
				minimoIntervalo = 0.0d,
				maximoIntervalo = 0.0d,
				novaMedia = 0.0;
		
		// Tentativa de arrendondar os valores pra 6 casas		
		novaMedia = medias.size();
		
		for(Double media : medias)
		{
			if(media.isNaN() || media <= 0.0){
				novaMedia--;
			}else{
				mediaDasMedias += media / novaMedia;
				somatorioVariancia += media * media;
			}
		}
		
		System.out.println("Media: " + mediaDasMedias);
		
		
		if(Config.NUMERO_RODADAS != 1)
		{
			desvioPadrao = Math.sqrt(
									(somatorioVariancia - mediaDasMedias * mediaDasMedias * novaMedia) /
									(novaMedia - 1)
								);
		
			double intervalo = 1.96 * desvioPadrao / novaMedia;		
			
			minimoIntervalo = mediaDasMedias - intervalo;
			maximoIntervalo = mediaDasMedias + intervalo;			
			
			System.out.println("Desvio-Padrao: " + desvioPadrao);

			System.out.println("Minimo: " + (minimoIntervalo));
			System.out.println("Maximo: " + (maximoIntervalo));
			
			System.out.println("Porcentagem: " + (intervalo * 100 / mediaDasMedias) + "%\n");
		}
	}	
}


/*

Controlador.java

*/

package controller;

import java.util.ArrayList;
import java.util.Collection;

import main.Config;
import model.Fila;
import model.Cliente;
import model.Resultado;
import model.Servidor;

public class Controlador {

	//Constante que define que o evento ocorrido eh uma chegada
	private static final String CHEGADA ="chegada";

	//Constante que define que o evento ocorrido eh o fim de um servico
	private static final String SERVICO ="servico";

	//Filas do sistema
	private Fila fila1,fila2;

	//Servidor
	private Servidor servidor;

	// Classe construtora do Controlador, setando as variaveis iniciais
	public Controlador(long sementeTemp , float taxaTemp, float taxaServidor)
	{
		//System.out.println("Semente servidor : " + semente);
		long semente = sementeTemp * 100 + System.currentTimeMillis() % 40;

		servidor = new Servidor(semente,taxaServidor);
		
		fila1 = new Fila(sementeTemp, taxaTemp);

		/*
		 * Importante ressaltar que a Fila 2 nao tem que gerar chegadas por
		 * isso o construtor eh diferente
		 */
		fila2 = new Fila();
	}

	// Executa uma rodada, retornando um Resultado
	public Resultado executarRodada()
	{		
		// Nessa variáveis que armazenaremos o somatório
		// dos tempos para calcular a Média da Rodada
		double somatorioTempoEsperaFila1 = 0.0d,
			   	somatorioTempoEsperaFila2 = 0.0d,
				somatorioTempoServico1 = 0.0d,
				somatorioTempoServico2 = 0.0d,
				somatorioVariancia1 = 0.0d,
				somatorioVariancia2 = 0.0d;
		
		// Armazena cada um dos tempos de cada cliente 
		Collection<Double> 	w1 = new ArrayList<Double>(),
							w2 = new ArrayList<Double>(),
							t1 = new ArrayList<Double>(),
							t2 = new ArrayList<Double>(),
							nq1 = new ArrayList<Double>(),
							nq2 = new ArrayList<Double>(),
							n1 = new ArrayList<Double>(),
							n2 = new ArrayList<Double>(),
							variancias1 = new ArrayList<Double>(),
							variancias2 = new ArrayList<Double>();


		// Indicam o tempo que esta sendo simulado, 
		// e o momento em que acabaram de ocorrer chegadas da fase transiente
		double tempoAtual = 0.0d,
				tempoUltimaChegadaTransiente = 0.0d;

		// Indica o numero de chegadas que ocorreram, 
		// incluindo os transientes, eh utilizado para saber se a fase transiente acabou
		int numeroChegadas = 0;

		// Caculando quando ocorrera a primeira chegada
		double chegadaFila1 = fila1.calculaProximaChegada();

		Eventos.getInstance().addEvento(chegadaFila1, CHEGADA);

		// A partir daqui, o programa fica lendo sempre a lista de eventos e servindo 
		// o próximo cliente, dependendo da situação atual do sistema
		while(fila1.getNumeroTotalDeChegadas() < Config.TAMANHO_RODADA)
		{			
			try{
				tempoAtual = Eventos.getInstance().getTempoProximoEvento();
			}catch(Exception e) {
			}
			
			// Remove o proximo evento da fila, retornando o seu tipo
			String tipoEvento = Eventos.getInstance().removeProximoEvento();

			// Trata eventos do tipo 'Chegada'
			if(tipoEvento.contains(CHEGADA))
			{
				// Se a chegada estiver dentro do intervalo da Fase Transiente, 
				// adiciono a fila um cliente do tipo transiente
				// Caso contrario, adiciono um client e normal
				if(numeroChegadas < Config.TAMANHO_FASE_TRANSIENTE)
					fila1.adicionaCliente(new Cliente(tempoAtual,Config.TIPO_TRANSIENTE));
				else
				{
					if(numeroChegadas == Config.TAMANHO_FASE_TRANSIENTE)
						tempoUltimaChegadaTransiente = tempoAtual;

					fila1.adicionaCliente(new Cliente(tempoAtual,"naoTransiente"));
				}

				// Trato a nova chegada na fila
				chegadaFila1 = fila1.calculaProximaChegada();
				numeroChegadas++;

				Eventos.getInstance().addEvento(tempoAtual + chegadaFila1, CHEGADA);
				
				if (Config.TESTE_DE_CORRECAO)
					System.out.println(">> Numero de Chegadas:" + numeroChegadas);
								
				// Se nenhum cliente estiver sendo servido...
				if(servidor.getClienteSendoServido() == null)
				{
					Cliente cliente = fila1.removerParaAtendimento();
					
					if (cliente == null) 
						System.out.println("Fila 1 está vazia.");					
					
					// Seto o W1 do cliente corrente
					cliente.setTempoSaidaFila1(tempoAtual);

					// Ocorreu uma chegada e ninguem esta sendo servido,
					// eu ja encaminho para o servidor
					servidor.setClienteSendoServido(cliente);

					double tempoServico = servidor.calculaTempoDeServico();

					Eventos.getInstance().addEvento(tempoAtual + tempoServico, SERVICO);
					
					if (Config.TESTE_DE_CORRECAO) 
					{
						System.out.println("\nEvento: Servidor Vazio");
						System.out.println("Tipo Evento:" + tipoEvento);
						System.out.println("Cliente a ser Servido:" + cliente);
						System.out.println("Fila de Origem:" + cliente.getFilaDeOrigem());
						System.out.println("Tempo: " + tempoAtual);
					}					
				}
				else
				{
					Cliente cliente_sendo_servido = servidor.getClienteSendoServido();

					if(cliente_sendo_servido.getFilaDeOrigem().equalsIgnoreCase("fila2"))
					{
						Cliente cliente_interrompido = servidor.getClienteSendoServido();
								
						double tempoServido = tempoAtual - cliente_interrompido.getTempoSaidaFila2();
						double tempoDeServicoResidual = cliente_sendo_servido.getInterrompido() ?
								cliente_interrompido.getTempoResidual() : cliente_interrompido.getTempoServico2();
						
						cliente_interrompido.setTempoResidual(tempoDeServicoResidual - tempoServido);
						
						Eventos.getInstance().removeEvento(tempoAtual + cliente_interrompido.getTempoResidual());
						
						cliente_interrompido.setInterrompido(true);
						
						servidor.setClienteSendoServido(null);

						fila2.adicionaClienteInicio(cliente_interrompido);
						
						// Volto a servir o cliente normalmente
						Cliente cliente = fila1.removerParaAtendimento();
						
						// Seto o W1 do cliente corrente
						cliente.setTempoSaidaFila1(tempoAtual);

						// Ocorreu uma chegada e ninguem esta sendo servido,
						// eu ja encaminho para o servidor
						servidor.setClienteSendoServido(cliente);

						double tempoServico = servidor.calculaTempoDeServico();

						Eventos.getInstance().addEvento(tempoAtual + tempoServico, SERVICO);
						
						if (Config.TESTE_DE_CORRECAO) 
						{
							System.out.println("\nEvento: PREEMPÇÃO");
							System.out.println("Tipo Evento:" + tipoEvento);
							System.out.println("Cliente Interrompido:" + cliente_interrompido);
							System.out.println("Fila de Origem:" + cliente_interrompido.getFilaDeOrigem());							
							System.out.println("Cliente a ser Servido:" + cliente);
							System.out.println("Fila de Origem:" + cliente.getFilaDeOrigem());
							System.out.println("Tempo: " + tempoAtual);
						}						
					}
				}
			}
			// Trata eventos do tipo 'Servico'
			else if(tipoEvento.contains(SERVICO))
			{
				Cliente cliente_sendo_servido = servidor.getClienteSendoServido();
				
				if(Config.TESTE_DE_CORRECAO) 
				{
					System.out.println("\nEvento: Cliente sendo Servido");
					System.out.println("Tipo Evento:" + tipoEvento);
					System.out.println("Cliente sendo Servido:" + cliente_sendo_servido);
					System.out.println("Fila de Origem:" + cliente_sendo_servido.getFilaDeOrigem());
					System.out.println("Tempo: " + tempoAtual);
				}

				// Se o cliente estiver saindo da Fila 1
				if(cliente_sendo_servido.getFilaDeOrigem().equalsIgnoreCase("fila1"))
				{
					cliente_sendo_servido.setTempoChegadaFila2(tempoAtual);

					// Contabilizando apenas clientes que não são transientes
					if(!cliente_sendo_servido.getTipo().equals(Config.TIPO_TRANSIENTE))
						servidor.addNumeroClientesServidosFila1();

					// Se o cliente que terminou o servico veio da fila 1 ele vai pra fila 2
					fila2.adicionaCliente(cliente_sendo_servido);
				}
				// Se o cliente estiver saindo da Fila 2
				// É aqui que seus respectivos tempos são finalmente armazenados
				else
				{
					cliente_sendo_servido.setTempoSaida(tempoAtual);

					if(!cliente_sendo_servido.getTipo().equals(Config.TIPO_TRANSIENTE))
					{
						//Contabilizando apenas clientes que não são transientes
						servidor.addNumeroClientesServidosFila2();
						
						double tempoEsperaFila1 = cliente_sendo_servido.getTempoSaidaFila1() -
						cliente_sendo_servido.getTempoChegadaFila1();
						
						double variancia1 = tempoEsperaFila1 * tempoEsperaFila1;
						
						double tempoServico1 = cliente_sendo_servido.getTempoChegadaFila2() - 
						cliente_sendo_servido.getTempoSaidaFila1();
						
						double tempoTotal1 = tempoEsperaFila1 + tempoServico1;
						
						somatorioTempoEsperaFila1 += tempoEsperaFila1;
						somatorioVariancia1 += variancia1;
						somatorioTempoServico1 += tempoServico1;
						
						double tempoEsperaFila2 = (cliente_sendo_servido.getTempoSaida() - 
													cliente_sendo_servido.getTempoChegadaFila2() -
													cliente_sendo_servido.getTempoServico2());

						double variancia2 = tempoEsperaFila2 * tempoEsperaFila2;
						
						double tempoServico2 = cliente_sendo_servido.getTempoServico2();
						
						double tempoTotal2 = tempoEsperaFila2 + tempoServico2;
						
						somatorioTempoEsperaFila2 += tempoEsperaFila2;											  
						somatorioVariancia2 += variancia2;
						somatorioTempoServico2 += tempoServico2;	
						
						// Utilizaremos os dados seguintes para obter o cálculo da Fase Transiente
						// Calculando a média acumulada de cada um das Collections
						// gerando a média acumulada e plotando seus respectivos gráficos
						if (Config.ESTIMAR_FASE_TRANSIENTE) 
						{
							double tamanhoFila1	  = fila1.getNumeroTotalDeChegadas() / 
							(tempoAtual - tempoUltimaChegadaTransiente) * tempoEsperaFila1;

							double tamanhoFila2	  = fila2.getNumeroTotalDeChegadas() /
													(tempoAtual - tempoUltimaChegadaTransiente) * tempoEsperaFila2;
							
							double tamanhoTotalFila1 = fila1.getNumeroTotalDeChegadas() / 
														(tempoAtual - tempoUltimaChegadaTransiente) * (tempoTotal1);
							
							double tamanhoTotalFila2 = fila2.getNumeroTotalDeChegadas() /
														(tempoAtual - tempoUltimaChegadaTransiente) * (tempoTotal2);
							
							w1.add(tempoEsperaFila1);
							w2.add(tempoEsperaFila2);
							t1.add(tempoEsperaFila1 + tempoServico1);
							t2.add(tempoEsperaFila2 + tempoServico2);
							nq1.add(tamanhoFila1);
							nq2.add(tamanhoFila2);
							n1.add(tamanhoTotalFila1);
							n2.add(tamanhoTotalFila2);
							variancias1.add(variancia1);
							variancias2.add(variancia2);	
						}
						
					}

					servidor.setClienteSendoServido(null);
				}

				// Se a Fila1 tiver clientes, atendo eles primeiramente
				// pois possuem maior prioridade
				if(!fila1.isEmpty())
				{
					Cliente cliente = fila1.removerParaAtendimento();
					cliente.setTempoSaidaFila1(tempoAtual);

					//Se tiver alguem da fila 1 para ser atendido, ele possui prioridade
					servidor.setClienteSendoServido(cliente);

					cliente.setFilaDeOrigem("fila1");

					double tempoServico = servidor.calculaTempoDeServico();

					Eventos.getInstance().addEvento(tempoAtual + tempoServico, SERVICO);
					
					if(Config.TESTE_DE_CORRECAO) 
					{
						System.out.println("\nEvento: Fila1 com Clientes.");
						System.out.println("Tipo Evento:" + tipoEvento);
						System.out.println("Cliente Servido:" + cliente_sendo_servido);
						System.out.println("Fila de Origem:" + cliente_sendo_servido.getFilaDeOrigem());
						System.out.println("Cliente a ser Servido:" + cliente);
						System.out.println("Fila de Origem:" + cliente.getFilaDeOrigem());
						System.out.println("Tempo: " + tempoAtual);
					}
				}

				// Se a Fila1 estiver vazia e ainda haver clientes na Fila2,
				// comeco a atende-los
				else if(!fila2.isEmpty())
				{
					Cliente cliente = fila2.removerParaAtendimento();
					
					double tempoServico;
					
					if (!cliente.getInterrompido())
					{
						cliente.setTempoServico2(servidor.calculaTempoDeServico());
						tempoServico = tempoAtual + cliente.getTempoServico2();
					
						if (Config.TESTE_DE_CORRECAO)
							System.out.println("\nEvento: Fila1 vazia com Cliente2 Normal.");	
					}						
					else
					{
						tempoServico = tempoAtual + cliente.getTempoResidual();
						
						if (Config.TESTE_DE_CORRECAO)
							System.out.println("\nEvento: Fila1 vazia com Cliente2 Residual.");
					}
					
					cliente.setTempoSaidaFila2(tempoAtual);					
					servidor.setClienteSendoServido(cliente);
					cliente.setFilaDeOrigem("fila2");

					Eventos.getInstance().addEvento(tempoServico, SERVICO);
						
					if(Config.TESTE_DE_CORRECAO) 
					{						
						System.out.println("Tipo Evento:" + tipoEvento);
						System.out.println("Cliente a ser Servido:" + cliente);
						System.out.println("Fila de Origem:" + cliente.getFilaDeOrigem());
						System.out.println("Tempo: " + tempoAtual);
					}					
				}
				else
				{
					// Alguma coisa deveria acontecer aqui?
					// Em teoria, nada deveria acontecer aqui
					// Apenas espera a proxima chegada na Fila
					if(Config.TESTE_DE_CORRECAO)
						System.out.println("\nEvento: As duas Filas estão vazias.\n");
				}

			} // -> Fim do tratamento do evento do tipo "Servico"
	

		} // -> Fim do While que loopa a Fila de Eventos
		
		// Finalmente calcula os tempos e tamanhos médios
		// de cada uma das variáveis do Sistema

		double tempoEsperaFila1 = somatorioTempoEsperaFila1 / servidor.getNumeroClientesServidosFila1(),
				tempoEsperaFila2 = somatorioTempoEsperaFila2 / servidor.getNumeroClientesServidosFila2();

    	double tempoServico1	= somatorioTempoServico1 / servidor.getNumeroClientesServidosFila1(),
				tempoServico2	= somatorioTempoServico2 / servidor.getNumeroClientesServidosFila2();

		double tempoTotalFila1	= tempoEsperaFila1 + tempoServico1,
				tempoTotalFila2	= tempoEsperaFila2 + tempoServico2;

		double tamanhoMedioFila1	  = fila1.getNumeroTotalDeChegadas() / 
										(tempoAtual - tempoUltimaChegadaTransiente) * tempoEsperaFila1;

		double tamanhoMedioFila2	  = fila2.getNumeroTotalDeChegadas() /
										(tempoAtual - tempoUltimaChegadaTransiente) * tempoEsperaFila2;

		double tamanhoMedioTotalFila1 = fila1.getNumeroTotalDeChegadas() / 
										(tempoAtual - tempoUltimaChegadaTransiente) * tempoTotalFila1;

		double tamanhoMedioTotalFila2 = fila2.getNumeroTotalDeChegadas() /
										(tempoAtual - tempoUltimaChegadaTransiente) * tempoTotalFila2;

		double varianciaFila1 = (somatorioVariancia1 - tempoEsperaFila1 * tempoEsperaFila1 * Config.TAMANHO_RODADA) / 
								(Config.TAMANHO_RODADA-1);

		double varianciaFila2 = (somatorioVariancia2 - tempoEsperaFila2 * tempoEsperaFila2 * Config.TAMANHO_RODADA) /
								(Config.TAMANHO_RODADA-1);
		
		if (Config.ESTIMAR_FASE_TRANSIENTE)
		{
			System.out.println("#>> Médias Acumuladas da Fase Transiente:");

			System.out.println("#N1:");
			imprimeMediaAcumulada(n1);

			System.out.println("#N2:");
			imprimeMediaAcumulada(n2);

			System.out.println("#T1:");
			imprimeMediaAcumulada(t1);

			System.out.println("#T2:");
			imprimeMediaAcumulada(t2);

			System.out.println("#Nq1:");
			imprimeMediaAcumulada(nq1);

			System.out.println("#Nq2:");
			imprimeMediaAcumulada(nq2);

			System.out.println("#W1:");
			imprimeMediaAcumulada(w1);

			System.out.println("#W2:");
			imprimeMediaAcumulada(w2);

			System.out.println("#Var(W1):");
			imprimeMediaAcumulada(variancias1);

			System.out.println("#Var(W2):");
			imprimeMediaAcumulada(variancias2);
		}

		return new Resultado(tamanhoMedioFila1,
							 tamanhoMedioFila2,
							 tempoTotalFila1,
							 tempoTotalFila2,
							 tamanhoMedioTotalFila1,
							 tamanhoMedioTotalFila2,
							 tempoEsperaFila1,
							 tempoEsperaFila2,
							 varianciaFila1,
							 varianciaFila2
							);
	}
	
	private static void imprimeMediaAcumulada(Collection<Double> media)
	{
		Double acumulado = 0.0d;
		int contador = 0;
		
		for(Double i : media)
		{
			contador++;
			acumulado += i;
			
			System.out.println((Double)(acumulado / contador));
		}
	}
}

/*

Eventos.java

*/


package controller;

import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Collection;


// Classe que irá lidar com todos os Eventos no Simulador
public class Eventos 
{
	private static Eventos instance;

	// Ele utiliza uma árvore rubro-negra para
	// fazer a ordenação na lista de Eventos
	private Eventos()
	{
		eventos = new TreeMap<Double,String>();
	}

	// Construtor Singleton
	public static Eventos getInstance()
	{
		if(instance==null)
			instance = new Eventos();

		return instance;
	}

	private SortedMap<Double, String> eventos;

	// Adiciona os eventos à Lista
	public void addEvento(Double tempo, String tipo)
	{
		if(!eventos.containsKey(tempo))
			eventos.put(tempo, tipo);
		else
		{
			// Tratando a possibilidade de ocorrerem
			// dois eventos
			String evento = eventos.get(tempo);

			// Concatenando os dois Eventos
			evento += "-" + tipo;
			removeEvento(tempo);
			eventos.put(tempo, tipo);
		}
	}

	public void removeEvento(Double tempo)
	{
		eventos.remove(tempo);
	}

	public Double getTempoProximoEvento()
	{
		return eventos.firstKey();
	}

	public String removeProximoEvento()
	{
		String tipoEvento = eventos.get(getTempoProximoEvento());
		removeEvento(getTempoProximoEvento());
		return tipoEvento;
	}

	public String removeProximoEvento(String tipo)
	{
		String tipoEvento = eventos.get(getTempoProximoEvento());
		removeEvento(getTempoProximoEvento());
		return tipoEvento;
	}
	
	public void removeTodosEventos()
	{
		this.eventos = new TreeMap<Double , String>();
	}
	
	public void imprimeListaEventos()
	{
		Collection<String> c = eventos.values();
		
		Iterator<String> itr = c.iterator();
		
		System.out.println("-------------");
		while(itr.hasNext())		
			System.out.println(itr.next());

	}
	
	public int count() {
		return this.eventos.size();
	}
	
	
}

/*

Config.java

*/

package main;

public class Config 
{	
	// Variável que define o tamanho da rodada (número de clientes)
	public static final int TAMANHO_RODADA = 1794;
	
	// Variável que define o número de rodadas
	public static final int NUMERO_RODADAS = 988;
	
	// Variável que define o tamanho (número de clientes da fase transiente)
	public static final int TAMANHO_FASE_TRANSIENTE = 20000;
		
	// String relacionada aos clientes da fase transiente
	public static final String TIPO_TRANSIENTE = "transiente";
	
	// Variável que ativa/desativa os output dos testes de correção do simulador
	public static final boolean TESTE_DE_CORRECAO = false;
	
	// Variável que ativa/desativa os outputs para  as estimativas da fase transiente
	public static final boolean ESTIMAR_FASE_TRANSIENTE = false;
	
	// Método que calcula o fator 
	public static int fator() 
	{
		return TAMANHO_RODADA * NUMERO_RODADAS + TAMANHO_FASE_TRANSIENTE;
	}
}

/*

Cliente.java

*/

package model;

public class Cliente 
{
	private String filaDeOrigem;
	
	//Diz se o cliente eh transiente, ou de qual rodada ele faz parte
	private String tipo;
	
	private double tempoChegadaFila1;
	private double tempoSaidaFila1;
	private double tempoChegadaFila2;
	private double tempoSaidaFila2;
	private double tempoSaida;
	private double tempoServico2;
	private boolean interrompido;
	private double tempoResidual;
	
	public Cliente(Double tempoChegadaNaFila, String tipo) 
	{
		this.filaDeOrigem = "fila1";
		this.tempoChegadaFila1  =  tempoChegadaNaFila;
		this.tipo = tipo;
		this.interrompido = false;

		tempoSaidaFila1 = 0.0;
		tempoChegadaFila2 = 0.0;
		tempoSaidaFila2 = 0.0;
		tempoSaida = 0.0;
		tempoServico2 = 0.0;
		tempoResidual = 0.0;		
	}

	public String getFilaDeOrigem() 
	{
		return filaDeOrigem;
	}
	
	public void setFilaDeOrigem(String filaDeOrigem) 
	{
		this.filaDeOrigem = filaDeOrigem;
	}

	public String getTipo() 
	{
		return tipo;
	}
	
	public double getTempoChegadaFila1() 
	{
		return tempoChegadaFila1;
	}

	public void setTempoChegadaFila1(double tempoChegadaFila1) 
	{
		this.tempoChegadaFila1 = tempoChegadaFila1;
	}

	public double getTempoSaidaFila1() 
	{
		return tempoSaidaFila1;
	}

	public void setTempoSaidaFila1(double tempoSaidaFila1) 
	{
		this.tempoSaidaFila1 = tempoSaidaFila1;
	}

	public double getTempoChegadaFila2() 
	{
		return tempoChegadaFila2;
	}

	public void setTempoChegadaFila2(double tempoChegadaFila2) 
	{
		this.tempoChegadaFila2 = tempoChegadaFila2;
	}

	public double getTempoSaidaFila2() 
	{
		return tempoSaidaFila2;
	}

	public void setTempoSaidaFila2(double tempoSaidaFila2) 
	{
		this.tempoSaidaFila2 = tempoSaidaFila2;
	}

	public double getTempoServico2()
	{
		return tempoServico2;
	}
	
	public void setTempoServico2(double tempoServico2)
	{
		this.tempoServico2 = tempoServico2;
	}

	public void adicionaTempoServico2(double tempoServico2) 
	{
		this.tempoServico2 += tempoServico2;
	}
		
	public double getTempoSaida() 
	{
		return tempoSaida;
	}

	public void setTempoSaida(double tempoSaida) 
	{
		this.tempoSaida = tempoSaida;
	}
	
	public boolean getInterrompido()
	{
		return interrompido;
	}
	
	public void setInterrompido(boolean foi)
	{
		this.interrompido = foi;
	}
	
	public double getTempoResidual() 
	{
		return tempoResidual;
	}

	public void setTempoResidual(double tempoResidual) 
	{
		this.tempoResidual = tempoResidual;
	}
}

/*

Fila.java

*/


package model;

import java.util.Collection;
import java.util.Random;
import java.util.LinkedList;

import main.Config;
import model.Cliente;

public class Fila
{
	private float taxa; 
	private Random geradorAleatorio; 
	protected Collection<Cliente> clientes; 
	protected int numeroTotalDeChegadas;
	
	// Cria uma fila
	public Fila() 
	{
		numeroTotalDeChegadas = 0;
		clientes = new LinkedList<Cliente>();
	}

	// Cria uma fila com uma semente e uma taxa
	public Fila(long semente,float taxa) 
	{
		// Iniciando a semente aleatoria
		geradorAleatorio = new Random(semente);

		this.taxa = taxa;

		numeroTotalDeChegadas = 0;

		clientes = new LinkedList<Cliente>();
	}
	
	// Calcula a proxima chegada
	public Double calculaProximaChegada()
	{
		// x = -ln(y)/LAMBDA
		return (double) - Math.log(geradorAleatorio.nextFloat()) / taxa;
	}
	
	// Verifica se a fila está vazia
	public boolean isEmpty()
	{
		return clientes.isEmpty();
	}
	
	// Cálcula o tamanho da fila
	public int size()
	{
		return clientes.size();
	}
	
	// Calcula o numero total de chegada 
	public int getNumeroTotalDeChegadas() 
	{
		return numeroTotalDeChegadas;
	}
	
	// Retorna a taxa da fila
	public float getTaxa() 
	{
		return taxa;
	}
	
	// Encaminha cliente para o atendimento
	public Cliente removerParaAtendimento() 
	{
		// Remove da lista o fregues que sera atendido
		return ((LinkedList<Cliente>)clientes).removeFirst();
	}
	
	// Adiciona cliente a fila
	public void adicionaCliente(Cliente fregues) 
	{
		if(!fregues.getTipo().equals(Config.TIPO_TRANSIENTE))
			numeroTotalDeChegadas++;

		((LinkedList<Cliente>)clientes).addLast(fregues);
	}
	
	// Adiciona cliente no inicio da fila. Utilizado pela preempção 
	public void adicionaClienteInicio(Cliente fregues) 
	{
		((LinkedList<Cliente>)clientes).addFirst(fregues);
	}
}

/*

Resultado.java

*/


package model;

// Classe que armazena o Resultado de cada Rodada
public class Resultado 
{
	private double tamanhoFila1;
	private double tamanhoFila2;
	private double tempoTotalFila1;
	private double tempoTotalFila2;
	private double tamanhoTotalFila1;
	private double tamanhoTotalFila2;
	private double tempoFila1;
	private double tempoFila2;
	private double varianciaTempoFila1;
	private double varianciaTempoFila2;
	
	// Instancia a Classe Resultado
	public Resultado(double tamanhoFila1,
					  double tamanhoFila2,
					  double tempoTotalFila1,
					  double tempoTotalFila2,
					  double tamanhoTotalFila1,
					  double tamanhoTotalFila2,
					  double tempoFila1,
					  double tempoFila2,
					  double varianciaTempoFila1,
					  double varianciaTempoFila2) 
	{ 
		super();
		this.tamanhoFila1 = tamanhoFila1;
		this.tamanhoFila2 = tamanhoFila2;
		this.tempoTotalFila1 = tempoTotalFila1;
		this.tempoTotalFila2 = tempoTotalFila2;
		this.tamanhoTotalFila1 = tamanhoTotalFila1;
		this.tamanhoTotalFila2 = tamanhoTotalFila2;
		this.tempoFila1 = tempoFila1;
		this.tempoFila2 = tempoFila2;
		this.varianciaTempoFila1 = varianciaTempoFila1;
		this.varianciaTempoFila2 = varianciaTempoFila2;
	}
	
	// Retorna o tamanho da Fila 1
	public double getTamanhoFila1() 
	{
		return tamanhoFila1;
	}
	
	// Define o tamanho da Fila 1, usando tamanhoFila1
	public void setTamanhoFila1(double tamanhoFila1) 
	{
		this.tamanhoFila1 = tamanhoFila1;
	}

	// Retorna o tamanho da Fila 2 
	public double getTamanhoFila2() 
	{
		return tamanhoFila2;
	}

	// Define o tamanho da Fila 2, usando tamanhoFila2
	public void setTamanhoFila2(double tamanhoFila2) 
	{
		this.tamanhoFila2 = tamanhoFila2;
	}
	
	// Retorna Tempo Total da Fila 1
	public double getTempoTotalFila1() 
	{
		return tempoTotalFila1;
	}
	
	// Define Tempo Total da Fila 1, usando tempoTotalFila1
	public void setTempoTotalFila1(double tempoTotalFila1) 
	{
		this.tempoTotalFila1 = tempoTotalFila1;
	}
	
	// Retorna Tempo Total da Fila 2
	public double getTempoTotalFila2() 
	{
		return tempoTotalFila2;
	}
	
	// Define o tempo total da Fila 2 usando tempoTotalFila2
	public void setTempoTotalFila2(double tempoTotalFila2) 
	{
		this.tempoTotalFila2 = tempoTotalFila2;
	}

	// Retorna tamanho total da Fila 1
	public double getTamanhoTotalFila1() 
	{
		return tamanhoTotalFila1;
	}
	
	// Define tamanho total da fila 1, usando tamanhoTotalFila1
	public void setTamanhoTotalFila1(double tamanhoTotalFila1) 
	{
		this.tamanhoTotalFila1 = tamanhoTotalFila1;
	}
	
	// Retorna tamanho total da Fila 2
	public double getTamanhoTotalFila2() 
	{
		return tamanhoTotalFila2;
	}
	
	// Define tamanho total da Fila 2, usando tamanhoTotalFila2 
	public void setTamanhoTotalFila2(double tamanhoTotalFila2) 
	{
		this.tamanhoTotalFila2 = tamanhoTotalFila2;
	}
	
	// Retorna Tempo Fila 1
	public double getTempoFila1() 
	{
		return tempoFila1;
	}

	// Define tempo Fila 1
	public void setTempoFila1(double tempoFila1) 
	{
		this.tempoFila1 = tempoFila1;
	}	
	
	// Retorna tempo fila 2
	public double getTempoFila2() 
	{
		return tempoFila2;
	}
	
	// Define tempo Fila 2, usando tempoFila2 
	public void setTempoFila2(double tempoFila2) 
	{
		this.tempoFila2 = tempoFila2;
	}
	
	// Retorna a Variância do Tempo da Fila 1
	public double getVarianciaTempoFila1() 
	{
		return varianciaTempoFila1;
	}
	
	// Define a Variância do Tempo da Fila 1, usando varianciaTempoFila1
	public void setVarianciaTempoFila1(double varianciaTempoFila1) 
	{
		this.varianciaTempoFila1 = varianciaTempoFila1;
	}

	// Retorna a Variância do Tempo da Fila 2	
	public double getVarianciaTempoFila2() 
	{
		return varianciaTempoFila2;
	}
	
	// Define a Variância do Tempo da Fila 2, usando varianciaTempoFila2
	public void setVarianciaTempoFila2(double varianciaTempoFila2) 
	{
		this.varianciaTempoFila2 = varianciaTempoFila2;
	}
}

/*

Servidor.java

*/

package model;

import java.util.Random;
import model.Cliente;

public class Servidor extends Thread
{
	private float taxa; 
	private Random geradorAleatorio; 
	private Cliente clienteSendoServido; 
	
	private float tempoTotalDeServico;
	private int numeroClientesServidosFila1;
	private int numeroClientesServidosFila2;
	
	// Instancia um novo servidor usando uma semente
	public Servidor(long semente, float taxaServidor)
	{
		geradorAleatorio = new Random(semente);
		this.taxa = taxaServidor;

		numeroClientesServidosFila1 = 0;
		numeroClientesServidosFila2 = 0;
		tempoTotalDeServico = 0.0f;
	}
	
	// Calcula o tempo de serviço
	public Double calculaTempoDeServico()
	{
		// x = -ln(y)/Lambda
		return (double)-Math.log(geradorAleatorio.nextFloat())/taxa;
		
		//return 1.0;
	}
	
	// Retorna tempo total de serviço
	public float getTempoTotalDeServico()
	{
		return this.tempoTotalDeServico;
	}
	
	// Retorna o número de clientes servidos
	public int getNumeroClientesServidos()
	{
		return numeroClientesServidosFila1+numeroClientesServidosFila2;
	}
	
	// Retorna o cliente que está sendo servido
	public Cliente getClienteSendoServido()
	{
		return clienteSendoServido;
	}
	
	// Define o cliente que está sendo serviço
	public void setClienteSendoServido(Cliente cliente)
	{
		this.clienteSendoServido = cliente;
	}
	
	// Atualiza o número de clientes servidos na fila 1 (+1)
	public void addNumeroClientesServidosFila1() 
	{
		this.numeroClientesServidosFila1++;
	}
	
	// Atualiza o número de clientes servidos na fila 2 (+1)
	public void addNumeroClientesServidosFila2() 
	{
		this.numeroClientesServidosFila2++;
	}
	
	// Retorna o número de clientes servidos na Fila 1
	public int getNumeroClientesServidosFila1() 
	{
		return numeroClientesServidosFila1;
	}
	
	// Retorna o número de clientes servidos na Fila 1
	public int getNumeroClientesServidosFila2() 
	{
		return numeroClientesServidosFila2;
	}
}

/*

EstatisticaTeorica.java

*/

package util;

public class EstatisticaTeorica
{
	static double taxa_chegada;

	public static void setTaxaChegada(float taxa)
	{
		taxa_chegada = taxa;
	}

	public static double n1_t() 
	{
		return taxa_chegada / (1 - taxa_chegada);		
	}
	
	public static double nq1_t()
	{
		return taxa_chegada * taxa_chegada / (1 - taxa_chegada);
	}
	
	public static double w1_t()
	{
		return n1_t();
	}

	public static double t1_t() 
	{
		return 1 / (1 - taxa_chegada);
	}
	
	public static double var_w1_t() 
	{
		return taxa_chegada * (2 - taxa_chegada) / ((1 - taxa_chegada) * (1 - taxa_chegada));
	}

	public static double w2_t()
	{
        return (3 * taxa_chegada + nq1_t() + n1_t()) / (1 - 2 * taxa_chegada);
	}

	public static double t2_t() 
	{
		return w2_t() + 1;
	}

	public static double n2_t() 
	{
		return taxa_chegada * t2_t();
	}
	
	public static double nq2_t()
	{
		return taxa_chegada * w2_t();
	}	
}


